﻿#pragma once
#include "XyNet/Common/FileSystem.h"
#include "XyNet/Common/SharedObject.h"
#include <boost/property_tree/ptree.hpp>

namespace XyNet
{
	/// 通用配置类，作为基类使用。因为两份配置会读写同一个文件，所以不能复制
	/// 只保存设置过的属性（隐藏一些不需要被看到的属性，提升安全性？）
	class XYNET_API Config : noncopyable
	{
	public:
		/// 传空路径表示不读写文件
		Config(const string& baseFileName = "");
		virtual ~Config();

		/// 恢复所有属性为默认值
		void restoreAllDefaultProperties();

	protected:
		/// 恢复默认属性值
		void restoreDefaultProperty(const string& name);
		/// 获取默认属性值
		template <typename T>
		T getDefaultProperty(const string& name) const;

		/// 设置默认属性，只有设置完默认属性才能使用
		template <typename T>
		void initializeProperty(const string& name, const T& defaultValue);
		/// 属性是否存在（设置了默认属性才算存在）
		bool hasProperty(const string& name) const;

		/// 获取属性
		template <typename T>
		T getProperty(const string& name) const;
		/// 设置属性
		template <typename T>
		void setProperty(const string& name, const T& value);

		/// 获取配置文件路径
		const FileSystem::Path& getPath() const;
		/// 获取属性树对象
		SharedObject<boost::property_tree::ptree>* getPropertyTreeObject();

		/// 载入配置
		void load();
		/// 保存配置
		void save();

		/// 载入配置实现
		virtual void loadImpl() = 0;
		/// 保存配置实现
		virtual void saveImpl() = 0;

	private:
		/// 配置文件路径
		FileSystem::Path _path;
		/// 属性树对象
		SharedObject<boost::property_tree::ptree> _propertyTreeObject;
		/// 默认属性树对象
		SharedObject<boost::property_tree::ptree> _defaultPropertyTreeObject;
		/// 是否有修改
		atomic<bool> _modified;
	};

	//////////////////////////////////////////////////////////////////////////

	/// xml配置类
	class XYNET_API XmlConfig : public Config
	{
	public:
		XmlConfig(const string& baseFileName = "");
		virtual ~XmlConfig();

		using Config::load;
		using Config::save;

	protected:
		virtual void loadImpl() override;
		virtual void saveImpl() override;
	};

	//////////////////////////////////////////////////////////////////////////

	/// json配置类
	class XYNET_API JsonConfig : public Config
	{
	public:
		JsonConfig(const string& baseFileName = "");
		virtual ~JsonConfig();

		using Config::load;
		using Config::save;

	protected:
		virtual void loadImpl() override;
		virtual void saveImpl() override;
	};

	//////////////////////////////////////////////////////////////////////////

	/// info配置类
	class XYNET_API InfoConfig : public Config
	{
	public:
		InfoConfig(const string& baseFileName = "");
		virtual ~InfoConfig();

		using Config::load;
		using Config::save;

	protected:
		virtual void loadImpl() override;
		virtual void saveImpl() override;
	};

	//////////////////////////////////////////////////////////////////////////

	template <typename T>
	inline T Config::getDefaultProperty(const string& name) const
	{
		assert(hasProperty(name));
		return _defaultPropertyTreeObject.createSharedLock()->getObject()->get<T>(name);
	}

	template <typename T>
	inline void Config::initializeProperty(const string& name, const T& defaultValue)
	{
		_defaultPropertyTreeObject.createUniqueLock()->getObject()->put(name, defaultValue);

		auto lock = _propertyTreeObject.createUpgradeLock();
		auto propertyTree = lock->getObject();
		if (propertyTree->find(name) == propertyTree->not_found())
			lock->toUnique()->put(name, defaultValue);
	}

	template <typename T>
	inline T Config::getProperty(const string& name) const
	{
		assert(hasProperty(name));
		auto lock = const_cast<SharedObject<boost::property_tree::ptree>&>(
			_propertyTreeObject).createUpgradeLock();
		auto propertyTree = lock->getObject();
		if (propertyTree->find(name) == propertyTree->not_found())
		{
			lock->toUnique()->put(name,
				_defaultPropertyTreeObject.createSharedLock()->getObject()->get<T>(name));
		}
		return propertyTree->get<T>(name);
	}

	template <>
	inline FileSystem::Path Config::getProperty(const string& name) const
	{
		return getProperty<string>(name);
	}

	template <typename T>
	inline void Config::setProperty(const string& name, const T& value)
	{
		assert(hasProperty(name));
		_propertyTreeObject.createUniqueLock()->getObject()->put(name, value);
		_modified = true;
	}

	template <>
	inline void Config::setProperty(const string& name, const FileSystem::Path& value)
	{
		setProperty(name, value.toUtf8());
	}
}

#define XYNET_CONFIG_CHECK_PROPERTY(name, type, defaultValue, check) \
public: \
inline void initialize##name() \
{ \
	auto value = defaultValue; \
	assert(check); \
	initializeProperty(#name, defaultValue); \
	_##name##Object = make_unique<SharedObject<type>>(defaultValue); \
} \
inline type get##name() const \
{ \
	if (!_##name##Object) \
		const_cast<Config*>(this)->initialize##name(); \
	return *_##name##Object->createSharedLock()->getObject(); \
} \
inline void set##name(const type& value) \
{ \
	if (!_##name##Object) \
		initialize##name(); \
	assert(check); \
	setProperty(#name, value); \
	*_##name##Object->createUniqueLock()->getObject() = value; \
} \
inline void restore##name() \
{ \
	if (!_##name##Object) \
		initialize##name(); \
	restoreDefaultProperty(#name); \
	*_##name##Object->createUniqueLock()->getObject() = getDefaultProperty<type>(#name); \
} \
protected: \
	unique_ptr<SharedObject<type>> _##name##Object;

#define XYNET_CONFIG_PROPERTY(name, type, defaultValue) \
	XYNET_CONFIG_CHECK_PROPERTY(name, type, defaultValue, 1)